/*
 * Copyright (C) 1997-2001 Id Software, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
 * USA.
 *
 * =======================================================================
 *
 * Upper layer of the keyboard implementation. This file processes all
 * keyboard events which are generated by the low level keyboard layer.
 * Remeber, that the mouse is handled by the refresher and not by the
 * client!
 *
 * =======================================================================
 */

#include "header/client.h"

/*
 * key up events are sent even if in console mode
 */

#define     MAXCMDLINE  256

char key_lines [ 32 ] [ MAXCMDLINE ];
int key_linepos;
int shift_down = false;
int anykeydown;

int edit_line = 0;
int history_line = 0;

int key_waiting;
char *keybindings [ K_LAST ];
qboolean consolekeys [ K_LAST ]; /* if true, can't be rebound while in console */
qboolean menubound [ K_LAST ]; /* if true, can't be rebound while in menu */
int keyshift [ K_LAST ];      /* key to map to if shift held down in console */
int key_repeats [ K_LAST ];      /* if > 1, it is autorepeating */
qboolean keydown [ K_LAST ];

qboolean    Cmd_IsComplete ( char *cmd );

typedef struct
{
	char    *name;
	int keynum;
} keyname_t;

keyname_t keynames[] = {
	{ "TAB", K_TAB },
	{ "ENTER", K_ENTER },
	{ "ESCAPE", K_ESCAPE },
	{ "SPACE", K_SPACE },
	{ "BACKSPACE", K_BACKSPACE },
	{ "UPARROW", K_UPARROW },
	{ "DOWNARROW", K_DOWNARROW },
	{ "LEFTARROW", K_LEFTARROW },
	{ "RIGHTARROW", K_RIGHTARROW },

	{ "ALT", K_ALT },
	{ "CTRL", K_CTRL },
	{ "SHIFT", K_SHIFT },

	{ "F1", K_F1 },
	{ "F2", K_F2 },
	{ "F3", K_F3 },
	{ "F4", K_F4 },
	{ "F5", K_F5 },
	{ "F6", K_F6 },
	{ "F7", K_F7 },
	{ "F8", K_F8 },
	{ "F9", K_F9 },
	{ "F10", K_F10 },
	{ "F11", K_F11 },
	{ "F12", K_F12 },

	{ "INS", K_INS },
	{ "DEL", K_DEL },
	{ "PGDN", K_PGDN },
	{ "PGUP", K_PGUP },
	{ "HOME", K_HOME },
	{ "END", K_END },

	{ "MOUSE1", K_MOUSE1 },
	{ "MOUSE2", K_MOUSE2 },
	{ "MOUSE3", K_MOUSE3 },
	{ "MOUSE4", K_MOUSE4 },
	{ "MOUSE5", K_MOUSE5 },

	{ "AUX1", K_AUX1 },
	{ "AUX2", K_AUX2 },
	{ "AUX3", K_AUX3 },
	{ "AUX4", K_AUX4 },
	{ "AUX5", K_AUX5 },
	{ "AUX6", K_AUX6 },
	{ "AUX7", K_AUX7 },
	{ "AUX8", K_AUX8 },
	{ "AUX9", K_AUX9 },
	{ "AUX10", K_AUX10 },
	{ "AUX11", K_AUX11 },
	{ "AUX12", K_AUX12 },
	{ "AUX13", K_AUX13 },
	{ "AUX14", K_AUX14 },
	{ "AUX15", K_AUX15 },
	{ "AUX16", K_AUX16 },
	{ "AUX17", K_AUX17 },
	{ "AUX18", K_AUX18 },
	{ "AUX19", K_AUX19 },
	{ "AUX20", K_AUX20 },
	{ "AUX21", K_AUX21 },
	{ "AUX22", K_AUX22 },
	{ "AUX23", K_AUX23 },
	{ "AUX24", K_AUX24 },
	{ "AUX25", K_AUX25 },
	{ "AUX26", K_AUX26 },
	{ "AUX27", K_AUX27 },
	{ "AUX28", K_AUX28 },
	{ "AUX29", K_AUX29 },
	{ "AUX30", K_AUX30 },
	{ "AUX31", K_AUX31 },
	{ "AUX32", K_AUX32 },

	{ "KP_HOME",         K_KP_HOME },
	{ "KP_UPARROW",      K_KP_UPARROW },
	{ "KP_PGUP",         K_KP_PGUP },
	{ "KP_LEFTARROW",    K_KP_LEFTARROW },
	{ "KP_5",            K_KP_5 },
	{ "KP_RIGHTARROW",   K_KP_RIGHTARROW },
	{ "KP_END",          K_KP_END },
	{ "KP_DOWNARROW",    K_KP_DOWNARROW },
	{ "KP_PGDN",         K_KP_PGDN },
	{ "KP_ENTER",        K_KP_ENTER },
	{ "KP_INS",          K_KP_INS },
	{ "KP_DEL",          K_KP_DEL },
	{ "KP_SLASH",        K_KP_SLASH },
	{ "KP_MINUS",        K_KP_MINUS },
	{ "KP_PLUS",         K_KP_PLUS },

	{ "MWHEELUP", K_MWHEELUP },
	{ "MWHEELDOWN", K_MWHEELDOWN },

	{ "PAUSE", K_PAUSE },

	{ "SEMICOLON", ';' }, /* because a raw semicolon seperates commands */

	{ NULL, 0 }
};

void
CompleteCommand ( void )
{
	char           *cmd, *s;

	s = key_lines [ edit_line ] + 1;

	if ( ( *s == '\\' ) || ( *s == '/' ) )
	{
		s++;
	}

	cmd = Cmd_CompleteCommand( s );

	if ( cmd )
	{
		key_lines [ edit_line ] [ 1 ] = '/';
		strcpy( key_lines [ edit_line ] + 2, cmd );
		key_linepos = strlen( cmd ) + 2;

		if ( Cmd_IsComplete( cmd ) )
		{
			key_lines [ edit_line ] [ key_linepos ] = ' ';
			key_linepos++;
			key_lines [ edit_line ] [ key_linepos ] = 0;
		}
		else
		{
			key_lines [ edit_line ] [ key_linepos ] = 0;
		}

		return;
	}
}

/*
 * Interactive line editing and console scrollback
 */
void
Key_Console ( int key )
{
	switch ( key )
	{
		case K_KP_SLASH:
			key = '/';
			break;
		case K_KP_MINUS:
			key = '-';
			break;
		case K_KP_PLUS:
			key = '+';
			break;
		case K_KP_HOME:
			key = '7';
			break;
		case K_KP_UPARROW:
			key = '8';
			break;
		case K_KP_PGUP:
			key = '9';
			break;
		case K_KP_LEFTARROW:
			key = '4';
			break;
		case K_KP_5:
			key = '5';
			break;
		case K_KP_RIGHTARROW:
			key = '6';
			break;
		case K_KP_END:
			key = '1';
			break;
		case K_KP_DOWNARROW:
			key = '2';
			break;
		case K_KP_PGDN:
			key = '3';
			break;
		case K_KP_INS:
			key = '0';
			break;
		case K_KP_DEL:
			key = '.';
			break;
		default:
			break;
	}

	if ( key == 'l' )
	{
		if ( keydown [ K_CTRL ] )
		{
			Cbuf_AddText( "clear\n" );
			return;
		}
	}

	if ( ( key == K_ENTER ) || ( key == K_KP_ENTER ) )
	{
		/* slash text are commands, else chat */
		if ( ( key_lines [ edit_line ] [ 1 ] == '\\' ) || ( key_lines [ edit_line ] [ 1 ] == '/' ) )
		{
			Cbuf_AddText( key_lines [ edit_line ] + 2 ); /* skip the > */
		}
		else
		{
			Cbuf_AddText( key_lines [ edit_line ] + 1 ); /* valid command */
		}

		Cbuf_AddText( "\n" );
		Com_Printf( "%s\n", key_lines [ edit_line ] );
		edit_line = ( edit_line + 1 ) & 31;
		history_line = edit_line;
		key_lines [ edit_line ] [ 0 ] = ']';
		key_linepos = 1;

		if ( cls.state == ca_disconnected )
		{
			SCR_UpdateScreen();  /* force an update, because the command may take some time */
		}

		return;
	}

	if ( key == K_TAB )
	{
		/* command completion */
		CompleteCommand();
		return;
	}

	if ( ( key == K_BACKSPACE ) || ( key == K_LEFTARROW ) || ( key == K_KP_LEFTARROW ) ||
		 ( ( key == 'h' ) && ( keydown [ K_CTRL ] ) ) )
	{
		if ( key_linepos > 1 )
		{
			key_linepos--;
		}

		return;
	}

	if ( key == K_DEL )
	{
		memmove( key_lines [ edit_line ] + key_linepos, key_lines [ edit_line ] + key_linepos + 1,
				sizeof ( key_lines [ edit_line ] ) - key_linepos - 1 );
		return;
	}

	if ( ( key == K_UPARROW ) || ( key == K_KP_UPARROW ) ||
		 ( ( key == 'p' ) && keydown [ K_CTRL ] ) )
	{
		do
		{
			history_line = ( history_line - 1 ) & 31;
		}
		while ( history_line != edit_line &&
				!key_lines [ history_line ] [ 1 ] );

		if ( history_line == edit_line )
		{
			history_line = ( edit_line + 1 ) & 31;
		}

		strcpy( key_lines [ edit_line ], key_lines [ history_line ] );
		key_linepos = (int) strlen( key_lines [ edit_line ] );
		return;
	}

	if ( ( key == K_DOWNARROW ) || ( key == K_KP_DOWNARROW ) ||
		 ( ( key == 'n' ) && keydown [ K_CTRL ] ) )
	{
		if ( history_line == edit_line )
		{
			return;
		}

		do
		{
			history_line = ( history_line + 1 ) & 31;
		}
		while ( history_line != edit_line &&
				!key_lines [ history_line ] [ 1 ] );

		if ( history_line == edit_line )
		{
			key_lines [ edit_line ] [ 0 ] = ']';
			key_linepos = 1;
		}
		else
		{
			strcpy( key_lines [ edit_line ], key_lines [ history_line ] );
			key_linepos = (int) strlen( key_lines [ edit_line ] );
		}

		return;
	}

	if ( ( key == K_PGUP ) || ( key == K_KP_PGUP ) || ( key == K_MWHEELUP ) || ( key == K_MOUSE4 ) )
	{
		con.display -= 2;
		return;
	}

	if ( ( key == K_PGDN ) || ( key == K_KP_PGDN ) || ( key == K_MWHEELDOWN ) || ( key == K_MOUSE5 ) )
	{
		con.display += 2;

		if ( con.display > con.current )
		{
			con.display = con.current;
		}

		return;
	}

	if ( ( key == K_HOME ) || ( key == K_KP_HOME ) )
	{
		if ( keydown [ K_CTRL ] )
		{
			con.display = con.current - con.totallines + 10;
		}

		else
		{
			key_linepos = 1;
		}

		return;
	}

	if ( ( key == K_END ) || ( key == K_KP_END ) )
	{
		if ( keydown [ K_CTRL ] )
		{
			con.display = con.current;
		}

		else
		{
			key_linepos = (int) strlen( key_lines [ edit_line ] );
		}

		return;
	}

	if ( ( key < 32 ) || ( key > 127 ) )
	{
		return; /* non printable character */
	}

	if ( key_linepos < MAXCMDLINE - 1 )
	{
		int last;
		int length;

		length = strlen( key_lines [ edit_line ] );

		if ( length >= MAXCMDLINE - 1 )
		{
			return;
		}

		last = key_lines [ edit_line ] [ key_linepos ];

		memmove( key_lines [ edit_line ] + key_linepos + 1, key_lines [ edit_line ] + key_linepos, length - key_linepos );

		key_lines [ edit_line ] [ key_linepos ] = key;
		key_linepos++;

		if ( !last )
		{
			key_lines [ edit_line ] [ key_linepos ] = 0;
		}
	}
}

qboolean chat_team;
char chat_buffer [ MAXCMDLINE ];
int chat_bufferlen = 0;
int chat_cursorpos = 0;

void
Key_Message ( int key )
{
	char last;

	if ( ( key == K_ENTER ) || ( key == K_KP_ENTER ) )
	{
		if ( chat_team )
		{
			Cbuf_AddText( "say_team \"" );
		}

		else
		{
			Cbuf_AddText( "say \"" );
		}

		Cbuf_AddText( chat_buffer );
		Cbuf_AddText( "\"\n" );

		cls.key_dest = key_game;
		chat_bufferlen = 0;
		chat_buffer [ 0 ] = 0;
		chat_cursorpos = 0;
		return;
	}

	if ( key == K_ESCAPE )
	{
		cls.key_dest = key_game;
		chat_cursorpos = 0;
		chat_bufferlen = 0;
		chat_buffer [ 0 ] = 0;
		return;
	}

	if ( key == K_BACKSPACE )
	{
		if ( chat_cursorpos )
		{
			memmove( chat_buffer + chat_cursorpos - 1, chat_buffer + chat_cursorpos, chat_bufferlen - chat_cursorpos + 1 );
			chat_cursorpos--;
			chat_bufferlen--;
		}

		return;
	}

	if ( key == K_DEL )
	{
		if ( chat_bufferlen && ( chat_cursorpos != chat_bufferlen ) )
		{
			memmove( chat_buffer + chat_cursorpos, chat_buffer + chat_cursorpos + 1, chat_bufferlen - chat_cursorpos + 1 );
			chat_bufferlen--;
		}

		return;
	}

	if ( key == K_LEFTARROW )
	{
		if ( chat_cursorpos > 0 )
		{
			chat_cursorpos--;
		}

		return;
	}

	if ( key == K_HOME )
	{
		chat_cursorpos = 0;
		return;
	}

	if ( key == K_END )
	{
		chat_cursorpos = chat_bufferlen;
		return;
	}

	if ( key == K_RIGHTARROW )
	{
		if ( chat_buffer [ chat_cursorpos ] )
		{
			chat_cursorpos++;
		}

		return;
	}

	if ( ( key < 32 ) || ( key > 127 ) )
	{
		return; /* non printable charcter */
	}

	if ( chat_bufferlen == sizeof ( chat_buffer ) - 1 )
	{
		return; /* all full, this should never happen on modern systems */
	}

	memmove( chat_buffer + chat_cursorpos + 1, chat_buffer + chat_cursorpos, chat_bufferlen - chat_cursorpos + 1 );

	last = chat_buffer [ chat_cursorpos ];

	chat_buffer [ chat_cursorpos ] = key;

	chat_bufferlen++;
	chat_cursorpos++;

	if ( !last )
	{
		chat_buffer [ chat_cursorpos ] = 0;
	}
}

/*
 * Returns a key number to be used to index keybindings[] by looking at
 * the given string.  Single ascii characters return themselves, while
 * the K_* names are matched up.
 */
int
Key_StringToKeynum ( char *str )
{
	keyname_t   *kn;

	if ( !str || !str [ 0 ] )
	{
		return ( -1 );
	}

	if ( !str [ 1 ] )
	{
		return ( str [ 0 ] );
	}

	for ( kn = keynames; kn->name; kn++ )
	{
		if ( !Q_stricmp( str, kn->name ) )
		{
			return ( kn->keynum );
		}
	}

	return ( -1 );
}

/*
 * Returns a string (either a single ascii char, or a K_* name) for the
 * given keynum.
 */
char *
Key_KeynumToString ( int keynum )
{
	keyname_t   *kn;
	static char tinystr [ 2 ] = { 0 };

	if ( keynum == -1 )
	{
		return ( "<KEY NOT FOUND>" );
	}

	if ( ( keynum > 32 ) && ( keynum < 127 ) )
	{
		/* printable ascii */
		tinystr [ 0 ] = keynum;
		return ( tinystr );
	}

	for ( kn = keynames; kn->name; kn++ )
	{
		if ( keynum == kn->keynum )
		{
			return ( kn->name );
		}
	}

	return ( "<UNKNOWN KEYNUM>" );
}

void
Key_SetBinding ( int keynum, char *binding )
{
	char    *new;
	int l;

	if ( keynum == -1 )
	{
		return;
	}

	/* free old bindings */
	if ( keybindings [ keynum ] )
	{
		Z_Free( keybindings [ keynum ] );
		keybindings [ keynum ] = NULL;
	}

	/* allocate memory for new binding */
	l = strlen( binding );
	new = Z_Malloc( l + 1 );
	strcpy( new, binding );
	new [ l ] = 0;
	keybindings [ keynum ] = new;
}

void
Key_Unbind_f ( void )
{
	int b;

	if ( Cmd_Argc() != 2 )
	{
		Com_Printf( "unbind <key> : remove commands from a key\n" );
		return;
	}

	b = Key_StringToKeynum( Cmd_Argv( 1 ) );

	if ( b == -1 )
	{
		Com_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 ) );
		return;
	}

	Key_SetBinding( b, "" );
}

void
Key_Unbindall_f ( void )
{
	int i;

	for ( i = 0; i < K_LAST; i++ )
	{
		if ( keybindings [ i ] )
		{
			Key_SetBinding( i, "" );
		}
	}
}

void
Key_Bind_f ( void )
{
	int i, c, b;
	char cmd [ 1024 ];

	c = Cmd_Argc();

	if ( c < 2 )
	{
		Com_Printf( "bind <key> [command] : attach a command to a key\n" );
		return;
	}

	b = Key_StringToKeynum( Cmd_Argv( 1 ) );

	if ( b == -1 )
	{
		Com_Printf( "\"%s\" isn't a valid key\n", Cmd_Argv( 1 ) );
		return;
	}

	if ( c == 2 )
	{
		if ( keybindings [ b ] )
		{
			Com_Printf( "\"%s\" = \"%s\"\n", Cmd_Argv( 1 ), keybindings [ b ] );
		}

		else
		{
			Com_Printf( "\"%s\" is not bound\n", Cmd_Argv( 1 ) );
		}

		return;
	}

	/* copy the rest of the command line */
	cmd [ 0 ] = 0;  /* start out with a null string */

	for ( i = 2; i < c; i++ )
	{
		strcat( cmd, Cmd_Argv( i ) );

		if ( i != ( c - 1 ) )
		{
			strcat( cmd, " " );
		}
	}

	Key_SetBinding( b, cmd );
}

/*
 * Writes lines containing "bind key value"
 */
void
Key_WriteBindings ( FILE *f )
{
	int i;

	for ( i = 0; i < K_LAST; i++ )
	{
		if ( keybindings [ i ] && keybindings [ i ] [ 0 ] )
		{
			fprintf( f, "bind %s \"%s\"\n", Key_KeynumToString( i ), keybindings [ i ] );
		}
	}
}

void
Key_Bindlist_f ( void )
{
	int i;

	for ( i = 0; i < K_LAST; i++ )
	{
		if ( keybindings [ i ] && keybindings [ i ] [ 0 ] )
		{
			Com_Printf( "%s \"%s\"\n", Key_KeynumToString( i ), keybindings [ i ] );
		}
	}
}

void
Key_Init ( void )
{
	int i;

	for ( i = 0; i < 32; i++ )
	{
		key_lines [ i ] [ 0 ] = ']';
		key_lines [ i ] [ 1 ] = 0;
	}

	key_linepos = 1;

	/* init 128 bit ascii characters in console mode */
	for ( i = 32; i < 128; i++ )
	{
		consolekeys [ i ] = true;
	}

	consolekeys [ K_ENTER ] = true;
	consolekeys [ K_KP_ENTER ] = true;
	consolekeys [ K_TAB ] = true;
	consolekeys [ K_LEFTARROW ] = true;
	consolekeys [ K_KP_LEFTARROW ] = true;
	consolekeys [ K_RIGHTARROW ] = true;
	consolekeys [ K_KP_RIGHTARROW ] = true;
	consolekeys [ K_UPARROW ] = true;
	consolekeys [ K_KP_UPARROW ] = true;
	consolekeys [ K_DOWNARROW ] = true;
	consolekeys [ K_KP_DOWNARROW ] = true;
	consolekeys [ K_BACKSPACE ] = true;
	consolekeys [ K_HOME ] = true;
	consolekeys [ K_KP_HOME ] = true;
	consolekeys [ K_END ] = true;
	consolekeys [ K_KP_END ] = true;
	consolekeys [ K_PGUP ] = true;
	consolekeys [ K_KP_PGUP ] = true;
	consolekeys [ K_PGDN ] = true;
	consolekeys [ K_KP_PGDN ] = true;
	consolekeys [ K_SHIFT ] = true;
	consolekeys [ K_INS ] = true;
	consolekeys [ K_KP_INS ] = true;
	consolekeys [ K_KP_DEL ] = true;
	consolekeys [ K_KP_SLASH ] = true;
	consolekeys [ K_KP_PLUS ] = true;
	consolekeys [ K_KP_MINUS ] = true;
	consolekeys [ K_KP_5 ] = true;

	consolekeys [ '`' ] = false;
	consolekeys [ '~' ] = false;
	consolekeys [ '^' ] = false;

	for ( i = 0; i < K_LAST; i++ )
	{
		keyshift [ i ] = i;
	}

	for ( i = 'a'; i <= 'z'; i++ )
	{
		keyshift [ i ] = i - 'a' + 'A';
	}

	keyshift [ '1' ] = '!';
	keyshift [ '2' ] = '@';
	keyshift [ '3' ] = '#';
	keyshift [ '4' ] = '$';
	keyshift [ '5' ] = '%';
	keyshift [ '6' ] = '^';
	keyshift [ '7' ] = '&';
	keyshift [ '8' ] = '*';
	keyshift [ '9' ] = '(';
	keyshift [ '0' ] = ')';
	keyshift [ '-' ] = '_';
	keyshift [ '=' ] = '+';
	keyshift [ ',' ] = '<';
	keyshift [ '.' ] = '>';
	keyshift [ '/' ] = '?';
	keyshift [ ';' ] = ':';
	keyshift [ '\'' ] = '"';
	keyshift [ '[' ] = '{';
	keyshift [ ']' ] = '}';
	keyshift [ '`' ] = '~';
	keyshift [ '\\' ] = '|';

	menubound [ K_ESCAPE ] = true;

	for ( i = 0; i < 12; i++ )
	{
		menubound [ K_F1 + i ] = true;
	}

	/* register our functions */
	Cmd_AddCommand( "bind", Key_Bind_f );
	Cmd_AddCommand( "unbind", Key_Unbind_f );
	Cmd_AddCommand( "unbindall", Key_Unbindall_f );
	Cmd_AddCommand( "bindlist", Key_Bindlist_f );
}

/*
 * Called by the system between frames for both key up and key down events
 * Should NOT be called during an interrupt!
 */
void
Key_Event ( int key, qboolean down, unsigned time )
{
	char *kb;
	char cmd [ 1024 ];

	/* hack for modal presses */
	if ( key_waiting == -1 )
	{
		if ( down )
		{
			key_waiting = key;
		}

		return;
	}

	/* update auto-repeat status */
	if ( down )
	{
		key_repeats [ key ]++;

		if ( ( key != K_BACKSPACE ) &&
			 ( key != K_PAUSE ) &&
			 ( key != K_PGUP ) &&
			 ( key != K_KP_PGUP ) &&
			 ( key != K_PGDN ) &&
			 ( key != K_KP_PGDN ) &&
			 ( key_repeats [ key ] > 1 ) )
		{
			return; /* ignore most autorepeats */
		}

		if ( ( key >= 200 ) && !keybindings [ key ] && ( cls.key_dest != key_console ) )
		{
			Com_Printf( "%s is unbound, hit F4 to set.\n", Key_KeynumToString( key ) );
		}
	}
	else
	{
		key_repeats [ key ] = 0;
	}

	if ( key == K_SHIFT )
	{
		shift_down = down;
	}

	/* console key is hardcoded, so the user can never unbind it */
	if ( ( key == '^' ) || ( key == '~' ) || ( key == '`' ) )
	{
		if ( !down )
		{
			return;
		}

		Con_ToggleConsole_f();
		return;
	}

	/* any key during the attract mode will bring up the menu */
	if ( cl.attractloop && ( cls.key_dest != key_menu ) &&
			!( ( key >= K_F1 ) && ( key <= K_F12 ) ) )
	{
		key = K_ESCAPE;
	}

	/* menu key is hardcoded, so the user can never unbind it */
	if (!cls.disable_screen)
	{
		if ( key == K_ESCAPE )
		{
			if ( !down )
			{
				return;
			}

			if ( cl.frame.playerstate.stats [ STAT_LAYOUTS ] && ( cls.key_dest == key_game ) )
			{
				/* put away help computer / inventory */
				Cbuf_AddText( "cmd putaway\n" );
				return;
			}

			switch ( cls.key_dest )
			{
				case key_message:
					Key_Message( key );
					break;
				case key_menu:
					M_Keydown( key );
					break;
				case key_game:
				case key_console:
					M_Menu_Main_f();
					break;
				default:
					Com_Error( ERR_FATAL, "Bad cls.key_dest" );
			}

			return;
		}
	}

	/* track if any key is down for BUTTON_ANY */
	keydown [ key ] = down;

	if ( down )
	{
		if ( key_repeats [ key ] == 1 )
		{
			anykeydown++;
		}
	}
	else
	{
		anykeydown--;

		if ( anykeydown < 0 )
		{
			anykeydown = 0;
		}
	}

	/* key up events only generate commands if the game key binding is
	 * a button command (leading + sign). These will occur even in console mode,
	 * to keep the character from continuing an action started before a console
	 * switch. Button commands include the kenum as a parameter, so multiple
	 * downs can be matched with ups */
	if ( !down )
	{
		kb = keybindings [ key ];

		if ( kb && ( kb [ 0 ] == '+' ) )
		{
			Com_sprintf( cmd, sizeof ( cmd ), "-%s %i %i\n", kb + 1, key, time );
			Cbuf_AddText( cmd );
		}

		if ( keyshift [ key ] != key )
		{
			kb = keybindings [ keyshift [ key ] ];

			if ( kb && ( kb [ 0 ] == '+' ) )
			{
				Com_sprintf( cmd, sizeof ( cmd ), "-%s %i %i\n", kb + 1, key, time );
				Cbuf_AddText( cmd );
			}
		}

		return;
	}

	/* if not a consolekey, send to the interpreter no matter what mode is */
	if ( ( ( cls.key_dest == key_menu ) && menubound [ key ] ) ||
		 ( ( cls.key_dest == key_console ) && !consolekeys [ key ] ) ||
		 ( ( cls.key_dest == key_game ) && ( ( cls.state == ca_active ) || !consolekeys [ key ] ) ) )
	{
		kb = keybindings [ key ];

		if ( kb )
		{
			if ( kb [ 0 ] == '+' )
			{
				/* button commands add keynum and time as a parm */
				Com_sprintf( cmd, sizeof ( cmd ), "%s %i %i\n", kb, key, time );
				Cbuf_AddText( cmd );
			}
			else
			{
				Cbuf_AddText( kb );
				Cbuf_AddText( "\n" );
			}
		}

		return;
	}

	if ( !down )
	{
		return; /* other systems only care about key down events */
	}

	if ( shift_down )
	{
		key = keyshift [ key ];
	}

	switch ( cls.key_dest )
	{
		case key_message:
			Key_Message( key );
			break;
		case key_menu:
			M_Keydown( key );
			break;

		case key_game:
		case key_console:
			Key_Console( key );
			break;
		default:
			Com_Error( ERR_FATAL, "Bad cls.key_dest" );
	}
}

void
Key_ClearStates ( void )
{
	int i;

	anykeydown = false;

	for ( i = 0; i < K_LAST; i++ )
	{
		if ( keydown [ i ] || key_repeats [ i ] )
		{
			Key_Event( i, false, 0 );
		}

		keydown [ i ] = 0;
		key_repeats [ i ] = 0;
	}
}

int
Key_GetKey ( void )
{
	key_waiting = -1;

	while ( key_waiting == -1 )
	{
		Sys_SendKeyEvents();
	}

	return ( key_waiting );
}

